#include "util/file.h"

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include "util/log.h"

bool
sc_file_executable_exists(const char *file) {
    // 相当于echo $PATH，系统路径是一定有值的
    char *path = getenv("PATH");
    if (!path)
        return false;
    path = strdup(path);
    if (!path)
        return false;

    bool ret = false;
    size_t file_len = strlen(file);
    char *saveptr;
    // strtok_r()函数用于分割字符串。strtok_r是linux平台下的strtok函数的线程安全版
    for (char *dir = strtok_r(path, ":", &saveptr); dir;
            dir = strtok_r(NULL, ":", &saveptr)) {
        size_t dir_len = strlen(dir);
        char *fullpath = malloc(dir_len + file_len + 2);
        if (!fullpath)
        {
            LOG_OOM();
            continue;
        }
        // 从源内存地址的起始位置开始拷贝若干个字节到目标内存地址中，即从源source中拷贝n个字节到目标destin中
        memcpy(fullpath, dir, dir_len);
        fullpath[dir_len] = '/';
        memcpy(fullpath + dir_len + 1, file, file_len + 1);

        struct stat sb;
        bool fullpath_executable = stat(fullpath, &sb) == 0 &&
            sb.st_mode & S_IXUSR;
        free(fullpath);
        if (fullpath_executable) {
            ret = true;
            break;
        }
    }

    free(path);
    return ret;
}

char *
sc_file_get_executable_path(void) {
// <https://stackoverflow.com/a/1024937/1987178>
#ifdef __linux__
    // 路径最长的限制 PATH_MAX 系统的定义
    char buf[PATH_MAX + 1]; // +1 for the null byte

    /*
    readlink()会将参数path的 符号链接内容存储到参数buf所指的内存空间，
    返回的内容不是以\000作字符串结尾，但会将字符串的字符数返回，这使得添加\000变得简单。
    
    若参数bufsiz小于符号连接的内容长度，过长的内容会被截断，
    如果 readlink 第一个参数指向一个文件而不是 符号链接时，readlink 设 置errno 为 EINVAL 并返回 -1。 
    
    readlink()函数组合了open()、read()和close()的所有操作。

    返回值 ：执行成功则返回字符串的字符数，失败返回-1， 错误代码存于errno

    /proc/self/exe是一个符号链接，代表当前程序的绝对路径
    用readlink读取/proc/self/exe可以获取当前程序的绝对路径
    */    
    ssize_t len = readlink("/proc/self/exe", buf, PATH_MAX);
    if (len == -1) {
        /*
        perror(s) 用来将上一个函数发生错误的原因输出到标准设备(stderr)。
        参数 s 所指的字符串会先打印出，后面再加上错误原因字符串。
        此错误原因依照全局变量errno的值来决定要输出的字符串。在库函数中有个errno变量，每个errno值对应着以字符串表示的错误类型。
        */        
        perror("readlink");
        return NULL;
    }
    buf[len] = '\0';
    /*
    strdup()主要是拷贝字符串s的一个副本，由函数返回值返回，这个副本有自己的内存空间，和s不相干。strdup函数复制一个字符串，使用完后要记得删除在函数中动态申请的内存，strdup函数的参数不能为NULL，一旦为NULL，就会报段错误，因为该函数包括了strlen函数，而该函数参数不能是NULL。
    */    
    return strdup(buf);
#else
    // in practice, we only need this feature for portable builds, only used on
    // Windows, so we don't care implementing it for every platform
    // (it's useful to have a working version on Linux for debugging though)
    return NULL;
#endif
}

bool
sc_file_is_regular(const char *path) {
    struct stat path_stat;

    /*
    stat() 通过文件名filename获取文件信息，并保存在第二个参数所指的结构体stat中
    返回值:     执行成功则返回0，失败返回-1，错误代码存于errno
    */
    if (stat(path, &path_stat)) {
        perror("stat");
        return false;
    }

    /**
     *  path_stat.st_mode     st_mode;  文件对应的模式，文件，目录等

stat用来判断没有打开的文件,而fstat用来判断打开的文件.
我们使用最多的属性是st_mode.通过着属性我们可以判断给定的文件是一个普通文件还是一个目录,连接等等.可以使用下面几个宏来判断.

S_ISREG是否是一个常规文件.
S_ISDIR是否是一个目录
S_ISCHR是否是一个字符设备.
S_ISBLK是否是一个块设备
S_ISFIFO是否是一个FIFO文件.
S_ISSOCK是否是一个SOCKET文件. 
    */    
    return S_ISREG(path_stat.st_mode);
}

